package com.pancm.test.encrypt;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;

public class PasswordUtils {

    private static final int SALT_LENGTH = 8;
    private static final int ITERATIONS = 65536;
    private static final int KEY_LENGTH = 128;

    /**
     * 对密码进行加密
     *
     * @param password 明文密码
     * @return 加密后的密码（格式：{salt}{hashedPassword}）
     */
    public static String encryptPassword(String password) throws Exception {
        // 生成随机盐值
        byte[] salt = generateSalt();
        // 使用 PBKDF2 算法加密密码
        byte[] hashedPassword = hash(password, salt, ITERATIONS, KEY_LENGTH);
        // 将盐值和哈希值拼接成字符串（用冒号分隔）
        return bytesToHex(salt) + ":" + bytesToHex(hashedPassword);
    }

    /**
     * 验证密码是否匹配
     *
     * @param rawPassword 用户输入的明文密码
     * @param storedHash 存储的加密密码（格式：{salt}{hashedPassword}）
     * @return 如果匹配返回 true，否则返回 false
     */
    public static boolean verifyPassword(String rawPassword, String storedHash) throws Exception {
        // 分离盐值和哈希值
        String[] parts = storedHash.split(":");
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid stored hash format");
        }
        byte[] salt = hexToBytes(parts[0]);
        byte[] storedHashBytes = hexToBytes(parts[1]);

        // 使用相同的盐值重新计算哈希值
        byte[] computedHash = hash(rawPassword, salt, ITERATIONS, KEY_LENGTH);

        // 比较两个哈希值
        return constantTimeEquals(computedHash, storedHashBytes);
    }

    /**
     * 生成随机盐值
     *
     * @return 随机生成的盐值字节数组
     */
    private static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);
        return salt;
    }

    /**
     * 使用 PBKDF2 算法加密密码
     *
     * @param password 明文密码
     * @param salt 盐值
     * @param iterations 迭代次数
     * @param keyLength 密钥长度
     * @return 加密后的密码字节数组
     */
    private static byte[] hash(String password, byte[] salt, int iterations, int keyLength)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keyLength);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        return factory.generateSecret(spec).getEncoded();
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder(2 * bytes.length);
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    /**
     * 将十六进制字符串转换为字节数组
     *
     * @param hex 十六进制字符串
     * @return 字节数组
     */
    private static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }

    /**
     * 安全地比较两个字节数组是否相等（防止时间攻击）
     *
     * @param a 第一个字节数组
     * @param b 第二个字节数组
     * @return 如果两个字节数组相等返回 true，否则返回 false
     */
    private static boolean constantTimeEquals(byte[] a, byte[] b) {
        if (a.length != b.length) {
            return false;
        }
        int result = 0;
        for (int i = 0; i < a.length; i++) {
            result |= a[i] ^ b[i];
        }
        return result == 0;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(encryptPassword("Abc@123kms"));
    }
}